﻿using AutoMapper;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NewRetail.Application.Components.WeChat.Configurations;
using NewRetail.Application.Components.WeChat.QrCode.Model;
using NewRetail.Application.Components.WeChat.Services;
using NewRetail.Application.Components.WeChat.Services.HttpClients;
using NewRetail.Application.Core;
using NewRetail.Application.Utility;
using SKIT.FlurlHttpClient.Wechat.Api;
using SKIT.FlurlHttpClient.Wechat.Api.Models;
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace NewRetail.Application.Components.WeChat.QrCode.Services
{
    /// <summary>
    /// 二维码服务接口。
    /// </summary>
    public interface IQrCodeService : IApplicationService
    {
        /// <summary>
        /// 创建一个二维码。
        /// </summary>
        /// <param name="model">创建二维码模型。</param>
        /// <param name="appId">appId。</param>
        /// <param name="expireSeconds">expireSeconds。</param>
        /// <returns>创建二维码的结果模型。</returns>
        Task<(CreateQrCodeResultModel, string)> Create(CreateQrCodeModel model, string appId, uint expireSeconds = 60);

        /// <summary>
        /// 根据 <paramref name="ticket"/> 得到一个二维码信息。
        /// </summary>
        /// <param name="ticket">获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码。</param>
        /// <returns>获取二维码的结果模型。</returns>
        Task<GetQrCodeResultModel> GetQrCode(string ticket);

        /// <summary>
        /// 获取二维码访问的Url。
        /// </summary>
        /// <param name="ticket">获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码。</param>
        /// <returns>二维码Url地址。</returns>
        string GetQrCodeUrl(string ticket);
        /// <summary>
        /// 创建店铺/仓库二维码。
        /// </summary>
        /// <param name="sceneStr">warehouseId</param>
        /// <param name="pixel"></param>
        /// <returns></returns>
        Task<(bool, string, List<string>)> CreateWarehouseQrCode(string sceneStr, int pixel);
    }

    /// <summary>
    /// 二维码服务实现。
    /// </summary>
    public class QrCodeService : ApplicationService, IQrCodeService
    {
        #region Field

        private readonly ILogger _logger;
        private readonly IHttpContextAccessor _accessor;
        private readonly RedisClient _redisClient;
        private readonly IWeChatApiHttpClientFactory _wechatApiHttpClientFactory;
        private readonly IMapper _mapper;
        private readonly IWeChatAccessToken _weChatAccessToken;
        private readonly WeChatOptions _wechatOptions;
        private readonly IHostEnvironment _hostEnvironment;
        private readonly IWebHostEnvironment _hostingEnvironment;
        private const string _uploadFileFolder = "Upload";
        private readonly IRepository<Warehouse.Entity.Warehouse> _warehouseRepository;
        #endregion Field

        #region Constructor

        /// <summary>
        /// 初始化一个二维码服务实例。
        /// </summary>
        public QrCodeService(IHttpContextAccessor accessor, IWeChatApiHttpClientFactory wechatApiHttpClientFactory, RedisClient redisClient,
            IOptions<WeChatOptions> wechatOptions, IMapper mapper, IWeChatAccessToken weChatAccessToken, IHostEnvironment hostEnvironment,
            IWebHostEnvironment hostingEnvironment, IRepository<Warehouse.Entity.Warehouse> warehouseRepository, ILogger<QrCodeService> logger)
        {
            _accessor = accessor;
            _redisClient = redisClient;
            _wechatApiHttpClientFactory = wechatApiHttpClientFactory;
            _mapper = mapper;
            _logger = logger;
            _weChatAccessToken = weChatAccessToken;
            _wechatOptions = wechatOptions.Value;
            _hostEnvironment = hostEnvironment;
            _hostingEnvironment = hostingEnvironment;
            _warehouseRepository = warehouseRepository;
        }

        #endregion Constructor

        #region Implementation of IQrCodeService

        #region 创建一个带参数二维码
        /// <summary>
        /// 创建一个带参数二维码。
        /// </summary>
        /// <param name="model">创建二维码模型。</param>
        /// <param name="appId">appId。</param>
        /// <param name="expireSeconds">expireSeconds。</param>
        /// <returns>创建二维码的结果模型。</returns>
        public async Task<(CreateQrCodeResultModel, string)> Create(CreateQrCodeModel qrCodeModel, string appId, uint expireSeconds = 60)
        {
            var result = await CreateQrCode(qrCodeModel, appId, expireSeconds);
            return (result.Item1, result.Item2);

            //var errMsg = "";
            //var actionType = qrCodeModel.ActionName;            
            ////var expiresIn = qrCodeModel.ExpireSeconds;
            //var sceneId = (actionType == ActionNameType.QR_SCENE.ToString() || actionType == ActionNameType.QR_LIMIT_SCENE.ToString()) ? qrCodeModel.SceneId : 1;
            //var sceneString = (actionType == ActionNameType.QR_STR_SCENE.ToString() || actionType == ActionNameType.QR_LIMIT_STR_SCENE.ToString()) ? qrCodeModel.SceneString : "";
            //////获取Redis
            ////var redis = _redisClient.Connection.GetDatabase(2);
            ////var keyWechat = $"{ConstOptions.KeyWechatRedisPrefix}{appId}";
            //////获取redis里面键对应的值
            ////var keyWechatValue = await redis.StringGetAsync(keyWechat);
            //////转成对象
            ////var wechatAccessToken = keyWechatValue.IsNullOrEmpty ? null : UtilsHelper.DeserializeObject<WechatAccessTokenEntity>(keyWechatValue.ToString());

            ////appId为null则默认获取配置第一个
            //if (string.IsNullOrEmpty(appId))
            //    appId = _wechatOptions.Accounts?.FirstOrDefault().AppId;
            ////获取accessToken对象
            //var wechatAccessToken = await _weChatAccessToken.GetAccessTokenObject(appId);
            ////创建一个http
            //var wechatApiclient = _wechatApiHttpClientFactory.Create(appId);
            ////请求参数
            //var sceneValue = (actionType == ActionNameType.QR_SCENE.ToString() || actionType == ActionNameType.QR_LIMIT_SCENE.ToString()) ? new CgibinQrcodeCreateRequest.Types.Action.Types.Scene() { SceneId = sceneId } : new CgibinQrcodeCreateRequest.Types.Action.Types.Scene() { SceneString = sceneString };
            //var requestInfo = new CgibinQrcodeCreateRequest()
            //{
            //    AccessToken = wechatAccessToken?.AccessToken,
            //    ActionType = actionType,
            //    Action = new CgibinQrcodeCreateRequest.Types.Action()
            //    {
            //        Scene = sceneValue
            //    },
            //    ExpiresIn = (int)expireSeconds
            //};
            ////请求获取数据
            //var responseInfo = await wechatApiclient.ExecuteCgibinQrcodeCreateAsync(requestInfo, cancellationToken: _accessor.HttpContext.RequestAborted);
            //if (!responseInfo.IsSuccessful())
            //{
            //    _logger.LogWarning(
            //        "创建二维码失败（状态码：{0}，错误代码：{1}，错误描述：{2}）。",
            //        responseInfo.RawStatus, responseInfo.ErrorCode, responseInfo.ErrorMessage
            //    );
            //}
            //else
            //{
            //    errMsg = responseInfo.ErrorMessage;
            //}

            //var resultInfo = _mapper.Map<CreateQrCodeResultModel>(responseInfo);

            //return (resultInfo, errMsg);
        }
        #endregion

        #region 根据 ticket 得到一个二维码信息
        /// <summary>
        /// 根据 ticket 得到一个二维码信息。
        /// </summary>
        /// <param name="ticket">获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码。</param>
        /// <returns>获取二维码的结果模型。</returns>
        public async Task<GetQrCodeResultModel> GetQrCode(string ticket)
        {
            var data = await _wechatApiHttpClientFactory.GetAsync(GetQrCodeUrl(ticket));

            return new GetQrCodeResultModel
            {
                ContentLength = (long)data.Item2.ContentLength,
                ContentType = data.Item2.ContentType.ToString(),
                Data = data.Item1
            };
            //return null;
        }
        #endregion

        #region 获取二维码访问的Url
        /// <summary>
        /// 获取二维码访问的Url。
        /// </summary>
        /// <param name="ticket">获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码。</param>
        /// <returns>二维码Url地址。</returns>
        public string GetQrCodeUrl(string ticket)
        {
#if NET
            ticket = HttpUtility.UrlEncode(ticket);
#else
            ticket = UrlEncoder.Default.Encode(ticket);
#endif
            return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
        }
        #endregion

        #region 创建店铺/仓库二维码
        /// <summary>
        /// 创建店铺/仓库二维码。
        /// </summary>
        /// <param name="sceneStr">warehouseId</param>
        /// <param name="pixel"></param>
        /// <returns></returns>
        public async Task<(bool, string, List<string>)> CreateWarehouseQrCode(string sceneStr, int pixel)
        {
            //定义返回的地址列表
            var warehouseQrCodeUrlList = new List<string>();
            var result = (false, "失败", warehouseQrCodeUrlList);
            //定义生成失败的店铺/仓库
            var errWarehouseQrCodeList = new List<string>();
            //获取仓库/店铺信息
            var db = await _warehouseRepository.GetDbContextAsync();
            var warehouseList = await db.Set<Warehouse.Entity.Warehouse>().AsNoTracking().Where(x => x.isEnable == true).ToListAsync();
            //如果sceneStr为空，那么直接从warehouse表获取所有记录一个个生成
            if (string.IsNullOrEmpty(sceneStr))
            {
                foreach(var warehouse in warehouseList)
                {
                    var warehouseName = warehouse.warehouseName;
                    sceneStr = warehouse.Id;
                    //创建二维码图片保存
                    var ret = await GenerateWarehouseQrCode(sceneStr, warehouseName, pixel);
                    //生成失败将仓库/店铺加入错误列表,反之将返回地址加入地址列表
                    if (!ret.Item1)
                        errWarehouseQrCodeList.Add(warehouseName);
                    else
                        warehouseQrCodeUrlList.Add(ret.Item3);
                }
                //如果没有错误的那全部生成成功
                if (errWarehouseQrCodeList.Count == 0)
                    result = (true, "", warehouseQrCodeUrlList);
                else
                    result = (false, string.Join(",", errWarehouseQrCodeList.ToArray()), warehouseQrCodeUrlList);
            }
            else
            {
                var warehouse = warehouseList.FirstOrDefault(x => x.Id == sceneStr);
                if (warehouse != null)
                {
                    //创建二维码图片保存
                    var resultCreate = await GenerateWarehouseQrCode(sceneStr, warehouse.warehouseName, pixel);
                    //创建成功则将地址存起来返回
                    if (resultCreate.Item1)
                    {
                        result.Item1 = true;
                        result.Item2 = "";
                        result.Item3.Add(resultCreate.Item3);
                    }
                }
                else
                {
                    //仓库/店铺Id不存在则返回false
                    result = (false, "Id不存在！", warehouseQrCodeUrlList);
                }
            }
            return result;
        }
        #endregion

        #endregion Implementation of IQrCodeService

        #region 私有方法

        #region 生成店铺/仓库二维码
        /// <summary>
        /// 生成店铺/仓库二维码
        /// </summary>
        /// <param name="sceneStrId"></param>
        /// <param name="warehouseName"></param>
        /// <param name="pixel"></param>
        /// <returns></returns>
        private async Task<(bool, string, string)> GenerateWarehouseQrCode(string sceneStrId, string warehouseName, int pixel)
        {
            var result = (false, "生成失败", "");

            //创建文件夹
            var warehouseQrCodeFolder = "Warehouse";
            string webRootPath = _hostingEnvironment.WebRootPath;
            var fileFolder = Path.Combine(webRootPath/*_hostEnvironment.ContentRootPath*/, warehouseQrCodeFolder);
            if (!Directory.Exists(fileFolder))
                Directory.CreateDirectory(fileFolder);
            //生成文件名
            var fileName = string.Concat(warehouseName, "(", sceneStrId,").jpg");
            var filePath = Path.Combine(fileFolder, fileName);
            
            //服务地址
            var hostValue = _accessor.HttpContext.Request.Host.Value;
            //下载地址
            //var rFilePath = Path.Combine(warehouseQrCodeFolder, fileName);
            var rFilePath = $"{hostValue}/{warehouseQrCodeFolder}/{fileName}";
            //rFilePath = Path.Combine(hostValue, rFilePath);
            //如果对应店铺/仓库二维码存在则直接返回
            if (File.Exists(filePath))
                return (true, "", rFilePath);

            var expireSeconds = (uint)3600;
            //var qrCodeModel = new CreateTemporaryQrCodeModel(sceneStrId, expireSeconds);
            var qrCodeModel = new CreateForeverQrCodeModel(sceneStrId);
            //获取微信返回的url
            var resultWeChatQrCode = await CreateQrCode(qrCodeModel, null, expireSeconds);
            if (!string.IsNullOrEmpty(resultWeChatQrCode.Item2))
            {
                result = (false, "创建二维码失败!" + resultWeChatQrCode.Item2, null);
                _logger.LogError("创建二维码失败!" + resultWeChatQrCode.Item2);
            }
            else
            {
                ////如果对应店铺/仓库二维码存在则删除后重新生成
                //if (File.Exists(filePath))
                //{
                //    var delResult = DeleteQrCodeImg(filePath);
                //    //删除失败直接返回
                //    if (!delResult)
                //        return (false, "删除文件失败！");
                //}

                //成功后取出Url
                var url = resultWeChatQrCode.Item1.Url;
                if (!string.IsNullOrEmpty(url))
                {
                    url = HttpUtility.UrlDecode(url);

                    //生成图片
                    var bitmap = QRCoderHelper.GetPTQRCode(url, pixel);
                    using (var stream = new FileStream(filePath, FileMode.Create))
                    {
                        bitmap.Save(stream, ImageFormat.Jpeg);
                        // 清空缓冲区数据
                        stream.Flush();
                    }
                    result = (true, "", rFilePath);
                }
            }
            return result;
        }
        #endregion

        #region 微信创建一个带参数二维码
        /// <summary>
        /// 创建一个带参数二维码。
        /// </summary>
        /// <param name="model">创建二维码模型。</param>
        /// <param name="appId">appId。</param>
        /// <param name="expireSeconds">expireSeconds。</param>
        /// <returns>创建二维码的结果模型。</returns>
        private async Task<(CreateQrCodeResultModel, string)> CreateQrCode(CreateQrCodeModel qrCodeModel, string appId, uint expireSeconds = 60)
        {
            var errMsg = "";
            var actionType = qrCodeModel.ActionName;
            var sceneId = (actionType == ActionNameType.QR_SCENE.ToString() || actionType == ActionNameType.QR_LIMIT_SCENE.ToString()) ? qrCodeModel.SceneId : 1;
            var sceneString = (actionType == ActionNameType.QR_STR_SCENE.ToString() || actionType == ActionNameType.QR_LIMIT_STR_SCENE.ToString()) ? qrCodeModel.SceneString : "";
            
            //appId为null则默认获取配置第一个
            if (string.IsNullOrEmpty(appId))
                appId = _wechatOptions.Accounts?.FirstOrDefault().AppId;
            //获取accessToken对象
            var wechatAccessToken = await _weChatAccessToken.GetAccessTokenObject(appId);
            //创建一个http
            var wechatApiclient = _wechatApiHttpClientFactory.Create(appId);
            //请求参数
            var sceneValue = (actionType == ActionNameType.QR_SCENE.ToString() || actionType == ActionNameType.QR_LIMIT_SCENE.ToString()) ? new CgibinQrcodeCreateRequest.Types.Action.Types.Scene() { SceneId = sceneId } : new CgibinQrcodeCreateRequest.Types.Action.Types.Scene() { SceneString = sceneString };
            var requestInfo = new CgibinQrcodeCreateRequest()
            {
                AccessToken = wechatAccessToken?.AccessToken,
                ActionType = actionType,
                Action = new CgibinQrcodeCreateRequest.Types.Action()
                {
                    Scene = sceneValue
                },
                ExpiresIn = (int)expireSeconds
            };
            //请求获取数据
            var responseInfo = await wechatApiclient.ExecuteCgibinQrcodeCreateAsync(requestInfo, cancellationToken: _accessor.HttpContext.RequestAborted);
            if (!responseInfo.IsSuccessful())
            {
                _logger.LogWarning(
                    "创建二维码失败（状态码：{0}，错误代码：{1}，错误描述：{2}）。",
                    responseInfo.RawStatus, responseInfo.ErrorCode, responseInfo.ErrorMessage
                );
                errMsg = responseInfo.ErrorMessage;
            }
            //else
            //{
            //    errMsg = responseInfo.ErrorMessage;
            //}

            var resultInfo = _mapper.Map<CreateQrCodeResultModel>(responseInfo);

            return (resultInfo, errMsg);
        }
        #endregion

        #region 将图片转成base64
        /// <summary>
        /// 将图片转成base64
        /// </summary>
        /// <returns></returns>
        private string FileToBase64(string url)
        {
            string base64String = string.Empty;
            string currentPath = Path.Combine(_hostEnvironment.ContentRootPath, url);
            if (File.Exists(currentPath))
            {
                using (FileStream SourceStream = new FileStream(currentPath, FileMode.Open, FileAccess.Read))
                {
                    byte[] bs = new byte[SourceStream.Length];
                    SourceStream.Read(bs, 0, bs.Length);
                    base64String = Convert.ToBase64String(bs);
                }
            }
            return base64String;
        }
        #endregion

        #region 删除二维码图片
        /// <summary>
        /// 删除二维码图片
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private bool DeleteQrCodeImg(string filePath)
        {
            var result = false;
            try
            {
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                    result = true;
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e.Message);
            }
            return result;
        }
        #endregion

        #endregion
    }
}
